/*
 * Copyright:  2018 smarabbit studio.
 *
 *  Licensed under the Confluent Community License; you may not use this file
 *  except in compliance with the License.  You may obtain a copy of the License at
 *
 *  http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:  2020年9月16日
 */
package com.massyframework.beanskin.web.filter;

import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.massyframework.beanskin.Module;
import com.massyframework.beanskin.service.ServiceNotFoundException;
import com.massyframework.beanskin.service.ServiceReference;
import com.massyframework.beanskin.service.ServiceRepository;
import com.massyframework.beanskin.service.ServiceTracker;
import com.massyframework.beanskin.service.ServiceTrackerCustomizer;
import com.massyframework.beanskin.util.classloading.ClassLoaderUtils;


/**
 * 代理的Filter
 * 
 * <p>
 * 做为占位符使用, 为真实的Filter先在ServletContext注册。当真实Filter实例化并注册为服务后，DelegateFilter将和实际的Filter进行绑定，然后将所有请求传法给真实的Filter处理。
 * 
 * @author huangkh
 *
 */
public class DelegateFilter extends ModuleFilter 
	implements ServiceTrackerCustomizer<Filter> {

	public static final String ALLOW_SKIP = "allowNotReadyToskip";
	private static final Logger LOG = LoggerFactory.getLogger(DelegateFilter.class);
	
    private ServiceTracker<Filter> tracker;
    private boolean allowSkip = true;
    private volatile ClassLoader classLoader;
    private volatile Filter internelFilter;
    private volatile ServletException initException;

    //调用次数
    private AtomicInteger invokeTimes;

    public DelegateFilter() {
        this.invokeTimes = new AtomicInteger();
    }

    /* (non-Javadoc)
     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        if (this.initException != null) {
            throw this.initException;
        }

        Filter filter = this.internelFilter;
        HttpServletRequest req = (HttpServletRequest)request;
        if (filter != null) {
            ClassLoader contextLaoder =
                    ClassLoaderUtils.setThreadContextClassLoader(this.classLoader);
            try {
                //计数加1
                this.invokeTimes.incrementAndGet();
                filter.doFilter(request, response, chain);
            }finally {
                //计数减1
                this.invokeTimes.decrementAndGet();
                ClassLoaderUtils.setThreadContextClassLoader(contextLaoder);
            }
        }else {
            //检查是否可跳过
            if (allowNotReadiedToSkip()) {
                if (LOG.isDebugEnabled()) {
                	LOG.debug("module is not readied, skip request: " + req.getRequestURI() + ".");
                }
                chain.doFilter(request, response);
            }else {
            	StringBuilder builder = new StringBuilder();
            	builder.append("cannot found service with filter:")
            		.append(this.getFilterConfig());
                throw new ServiceNotFoundException(builder.toString());
            }
        }
    }

    /**
     * 是否允许未就绪则跳过请求处理
     * @return {@link boolean},返回<code>true</code>表示允许跳过
     */
    protected boolean allowNotReadiedToSkip() {
        return this.allowSkip;
    }

    /* (non-Javadoc)
     * @see com.massyframework.assembly.web.filter.AbstractFilter#init()
     */
    @Override
    protected synchronized void init() throws ServletException {
        super.init();
        if (this.tracker == null){
            super.init();
            Module<?> module = this.getAssociatedModule();
            this.classLoader = module.getModuleClassLoader();
            if ("false".equalsIgnoreCase(this.getFilterConfig().getInitParameter(ALLOW_SKIP))){
                this.allowSkip = false;
            }

            String filterString = this.genericFilterString(module);
            this.tracker = new ServiceTracker<Filter>(
            		module,
                    Filter.class,
                    filterString,
                    this);
            this.tracker.open();
        }
    }

    /* (non-Javadoc)
     * @see com.massyframework.assembly.web.filter.AbstractFilter#destroy()
     */
    @Override
    public void destroy() {
        super.destroy();
        if (this.tracker != null){
            this.tracker.close();
            this.tracker = null;

            if (this.internelFilter != null) {
                //等待调用全部完成
                while (this.invokeTimes.get() != 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                    }
                }

                ClassLoader contextLoader =
                        ClassLoaderUtils.setThreadContextClassLoader(this.classLoader);
                try {
                    this.internelFilter.destroy();
                }finally {
                    ClassLoaderUtils.setThreadContextClassLoader(contextLoader);
                }
            }

            this.classLoader = null;
            this.internelFilter = null;
            this.initException = null;
        }
        this.invokeTimes = null;
    }

    /**
     * 生成过滤字符串
     * @return {@link String}
     */
    protected String genericFilterString(Module<?> module){
        StringBuilder builder = new StringBuilder();
        builder.append("(&")
                .append("(").append(ServiceReference.MODULE_GROUPID)
                .append("=").append(module.getGroupId()).append(")")
                .append("(").append(ServiceReference.MODULE_MODULEID)
                .append("=").append(module.getModuleId()).append(")")
                .append("(").append(ServiceReference.MODULE_VERSION)
                .append("=").append(module.getVersion().toString()).append(")")
                .append("(").append(ServiceReference.SERVICE_NAME)
                .append("=").append(this.getFilterConfig().getFilterName()).append(")")
                .append(")");
        return builder.toString();
    }

    /* (non-Javadoc)
     * @see com.massyframework.assembly.service.ServiceTrackerCustomizer#addService(com.massyframework.assembly.service.ServiceReference, com.massyframework.assembly.service.ServiceRepository)
     */
    @Override
    public synchronized Filter addService(ServiceReference<Filter> reference, ServiceRepository repository) {
        if (this.internelFilter == null) {
            Optional<Filter> optional = repository.getService(reference);
            optional = optional.map(filter ->{
                ClassLoader contextLoader =
                        ClassLoaderUtils.setThreadContextClassLoader(
                                this.classLoader);
                try {
                    this.invokeTimes.incrementAndGet();
                    filter.init(this.getFilterConfig());
                    this.initException = null;
                    return filter;
                }catch(ServletException e) {
                    if (LOG.isErrorEnabled()) {
                    	LOG.error(e.getMessage(), e);
                    }
                    this.initException = e;
                    return filter;
                }finally {
                    this.invokeTimes.decrementAndGet();
                    ClassLoaderUtils.setThreadContextClassLoader(contextLoader);
                }
            });
            this.internelFilter = optional.orElse(null);
            return this.internelFilter;
        }
        return null;
    }

    /* (non-Javadoc)
     * @see com.massyframework.assembly.service.ServiceTrackerCustomizer#removeService(com.massyframework.assembly.service.ServiceReference, java.lang.Object)
     */
    @Override
    public synchronized void removeService(ServiceReference<Filter> reference, Filter service) {
        if (this.internelFilter == service) {
            this.internelFilter = null;
            this.initException = null;

            //等待调用全部完成
            while (this.invokeTimes.get() != 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                }
            }

            ClassLoader contextLoader =
                    ClassLoaderUtils.setThreadContextClassLoader(this.classLoader);
            try {
                service.destroy();
            }finally {
                ClassLoaderUtils.setThreadContextClassLoader(contextLoader);
            }
        }
    }

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        Module<?> module = this.getAssociatedModule();
        return module == null ?
                this.getClass().getSimpleName() :
                this.getClass().getSimpleName() + "[module=" + module.toIdentifier() +  "].";
    }
}
